#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <pthread.h>
#include "shell.h"
#include "shell_io.h"
#include "shell_sw.h"
#include "shell_lib.h"
#include "shell_config.h"
#include "api_access.h"

#ifndef MARVELL_3236
#include "msJwSwitch.h"
#endif

a_uint32_t *ioctl_buf;
ssdk_init_cfg init_cfg = def_init_cfg;

static a_uint32_t *ioctl_argp;
static FILE * out_fd;
static char *err_info[] = 
{   
    "Operation succeeded",                 /*SW_OK*/            
    "Operation failed",                    /*SW_FAIL*/           
    "Illegal value ",                      /*SW_BAD_VALUE*/      
    "Value is out of range ",              /*SW_OUT_OF_RANGE*/    
    "Illegal parameter(s) ",               /*SW_BAD_PARAM*/       
    "Illegal pointer value ",              /*SW_BAD_PTR*/         
    "Wrong length",                        /*SW_BAD_LEN*/         
    "Wrong state of state machine ",       /*SW_BAD_STATE*/       
    "Read operation failed  ",             /*SW_READ_ERROR*/      
    "Write operation failed ",             /*SW_WRITE_ERROR*/    
    "Fail in creating an entry ",          /*SW_CREATE_ERROR*/  
    "Fail in deleteing an entry ",         /*SW_DELETE_ERROR*/     
    "Entry not found     ",                /*SW_NOT_FOUND*/       
    "The parameter(s) is the same  ",      /*SW_NO_CHANGE*/       
    "No more entry found  ",               /*SW_NO_MORE*/         
    "No such entry   ",                    /*SW_NO_SUCH*/         
    "Tried to create existing entry ",     /*SW_ALREADY_EXIST*/   
    "Table is full  ",                     /*SW_FULL*/           
    "Table is empty ",                     /*SW_EMPTY*/           
    "This request is not support    ",     /*SW_NOT_SUPPORTED*/   
    "This request is not implemented",     /*SW_NOT_IMPLEMENTED*/ 
    "The item is not initialized ",        /*SW_NOT_INITIALIZED*/
    "Operation is still running",          /*SW_BUSY*/            
    "Operation Time Out ",                 /*SW_TIMEOUT*/         
    "Operation is disabled ",              /*SW_DISABLE*/         
    "Resource not available (memory ...)", /*SW_NO_RESOURCE*/    
    "Error occured while INIT process",    /*SW_INIT_ERROR*/     
    "The other side is not ready yet",     /*SW_NOT_READY */      
    "Cpu memory allocation failed. ",      /*SW_OUT_OF_MEM */     
    "Operation has been aborted. ",        /*SW_ABORTED*/         
} ; 

void cmd_print_error(sw_error_t rtn)
{
    dprintf("Error: %s\n\n", err_info[abs(rtn)]);   
}

void cmd_print(char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    if(out_fd)
        vfprintf(out_fd, fmt, args);
    else
        vfprintf(stdout, fmt, args);
    va_end(args);
}

static sw_error_t cmd_input_parser(a_uint32_t *arg_val, a_uint32_t arg_index, sw_api_param_t *pp)
{
    a_int16_t i;
    a_uint32_t *pbuf;
    a_uint16_t rtn_size = 1;
    sw_api_param_t *pptmp = NULL;

    pbuf = ioctl_buf + rtn_size;    /*reserve for return value */

    for (i = 0; i < arg_index; i++) {
        pptmp = pp + i;
        if (pptmp->param_type & SW_PARAM_PTR) {
            pbuf += (pptmp->data_size + 3) / 4;
        }
    }
    if ((pbuf - ioctl_buf + (pptmp->data_size + 3) / 4) > (IOCTL_BUF_SIZE/4))
        return SW_NO_RESOURCE;

    *arg_val = (a_uint32_t) pbuf;

    return SW_OK;
}

static sw_error_t cmd_api_func(sw_api_func_t *fp, a_uint32_t nr_param, a_uint32_t * args)
{
    a_uint32_t *p = &args[2];
    sw_error_t rv;
    sw_error_t(*func) ();

    func = fp->func;

    switch (nr_param) {
    case 0:
        rv = (func) ();
        break;
    case 1:
        rv = (func) (p[0]);
        break;
    case 2:
        rv = (func) (p[0], p[1]);
        break;
    case 3:
        rv = (func) (p[0], p[1], p[2]);
        break;
    case 4:
        rv = (func) (p[0], p[1], p[2], p[3]);
        break;
    case 5:
        rv = (func) (p[0], p[1], p[2], p[3], p[4]);
        break;
    case 6:
        rv = (func) (p[0], p[1], p[2], p[3], p[4], p[5]);
        break;
    case 7:
        rv = (func) (p[0], p[1], p[2], p[3], p[4], p[5], p[6]);
        break;
    case 8:
        rv = (func) (p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
        break;
    case 9:
        rv = (func) (p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8]);
        break;
    case 10:
        rv = (func) (p[0], p[1], p[2], p[3], p[4], p[5],
                     p[6], p[7], p[8], p[9]);
        break;
    default:
        rv = SW_OUT_OF_RANGE;
    }

    *(a_uint32_t *) args[1] = rv;

    return rv;
}

static sw_error_t cmd_api_output(sw_api_param_t *pp, a_uint32_t nr_param, a_uint32_t * args)
{
    a_uint16_t i;
    a_uint32_t *pbuf;
    a_uint16_t rtn_size = 1;
    sw_error_t rtn = (sw_error_t) (*ioctl_buf);
    sw_api_param_t *pptmp = NULL;
    
    if (rtn != SW_OK) {
        cmd_print_error(rtn);
        return rtn;
    }

    pbuf = ioctl_buf + rtn_size;
    for (i = 0; i < nr_param; i++) {
        pptmp = pp + i;
        if (pptmp->param_type & SW_PARAM_PTR) {
            if (pptmp->param_type & SW_PARAM_OUT) {
                sw_data_type_t *data_type;
                if (!(data_type = cmd_data_type_find(pptmp->data_type)))
                    return SW_NO_SUCH;
                if (data_type->show_func) {
                    data_type->show_func(pptmp->param_name, pbuf, pptmp->data_size);
                } else {
                    dprintf("\nError, not define output print function!");
                }
            }
            if ((pbuf - ioctl_buf + (pptmp->data_size + 3) / 4) > (IOCTL_BUF_SIZE/4))
                return SW_NO_RESOURCE;
            pbuf += (pptmp->data_size + 3) / 4;
        }
    }
    return SW_OK;
}

void cmd_strtol(char *str, a_uint32_t * arg_val)
{
    if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
        sscanf(str, "%x", arg_val);
    else
        sscanf(str, "%d", arg_val);
}

static sw_error_t cmd_parse_api(char **cmd_str, a_uint32_t * arg_val)
{
    char *tmp_str;
    a_uint32_t arg_index, arg_start = 2, reserve_index = 1;
    a_uint32_t last_param_in = 0;
    a_uint32_t *temp;
    void *pentry;
    sw_api_param_t *pptmp = NULL;
    sw_api_t sw_api;
    a_uint32_t ignorecnt = 0;
    sw_api.api_id = arg_val[0];
    SW_RTN_ON_ERROR(sw_api_get(&sw_api));

    arg_val[arg_start] = get_devid();
    
    for (arg_index = reserve_index; arg_index < sw_api.api_nr; arg_index++) {
        tmp_str = NULL;
        pptmp = sw_api.api_pp + arg_index;

        if (!(pptmp->param_type & SW_PARAM_IN)) {
            ignorecnt++;
        }

        if (pptmp->param_type & SW_PARAM_IN) {
            tmp_str = cmd_str[arg_index - reserve_index - ignorecnt];
            last_param_in = arg_index;
        }
        temp = &arg_val[arg_start + arg_index];

        sw_data_type_t *data_type;
        if (!(data_type = cmd_data_type_find(pptmp->data_type)))
            return SW_NO_SUCH;

        pentry = temp;
        if (pptmp->param_type & SW_PARAM_PTR) {
            if (cmd_input_parser(temp, arg_index, sw_api.api_pp) != SW_OK)
                return SW_NO_RESOURCE;

            pentry = (void *) *temp;
        }

        if (pptmp->param_type & SW_PARAM_IN) {
#if 1
            if(pptmp->param_type & SW_PARAM_PTR) { //quiet mode
                if(!get_talk_mode())
                    set_full_cmdstrp((char **)(cmd_str + (last_param_in - reserve_index)));
            }
#endif     
            /*check and convert input param */
            if (data_type->param_check != NULL) {
                if (data_type->param_check(tmp_str, pentry, pptmp->data_size) != SW_OK)
                    return SW_BAD_PARAM;
            }
        }
    }

    /*superfluous args */
    /*
       if(cmd_str[last_param_in] != NULL)
       return SW_BAD_PARAM;
     */

    return SW_OK;
}

static sw_error_t cmd_parse_sw(char **cmd_str, a_uint32_t * arg_val)
{
    char *tmp_str;
    a_uint32_t arg_index = 0;
    a_uint32_t api_id = arg_val[0];

    tmp_str = cmd_str[arg_index];
    while (tmp_str) {
        arg_index++;
        cmd_strtol(tmp_str, &arg_val[arg_index]);
        tmp_str = cmd_str[arg_index];
    }

    /*args number check */
    if ( (arg_index == 0 && ( api_id == SW_CMD_VLAN_SHOW ||
                               api_id == SW_CMD_FDB_SHOW )) || 
      ( arg_index == 1 && api_id == SW_CMD_SET_DEVID) )
        return SW_OK;

    return SW_BAD_PARAM;
}

/*user command api*/
sw_error_t cmd_exec_api(a_uint32_t *arg_val)
{
    sw_error_t rv;
    sw_api_t sw_api;
    
    sw_api.api_id = arg_val[0];
    SW_RTN_ON_ERROR(sw_api_get(&sw_api));
    
    /*save cmd return value */
    arg_val[1] = (a_uint32_t) ioctl_buf;
    /*save set device id */
    arg_val[2] = get_devid();

    rv = cmd_api_func(sw_api.api_fp, sw_api.api_nr, arg_val);
    SW_RTN_ON_ERROR(rv);

    rv = cmd_api_output(sw_api.api_pp, sw_api.api_nr, arg_val);
    SW_RTN_ON_ERROR(rv);
    
    return rv;
}

void cmd_print_usage (int cmd_index, int cmd_index_sub)
{
    if(GCMD_NAME(cmd_index))
        dprintf("usage: %s", GCMD_NAME(cmd_index));

    if (GCMD_SUB_NAME(cmd_index, cmd_index_sub))
        dprintf(" %s", GCMD_SUB_NAME(cmd_index, cmd_index_sub));

    if(GCMD_SUB_ACT(cmd_index, cmd_index_sub) && GCMD_SUB_USAGE(cmd_index, cmd_index_sub))
        dprintf(" %s %s\n\n", GCMD_SUB_ACT(cmd_index, cmd_index_sub),
                              GCMD_SUB_USAGE(cmd_index, cmd_index_sub));
}

/*command string lookup*/
a_uint32_t cmd_lookup(char **cmd_str, int *cmd_index, int *cmd_index_sub)
{
    a_uint32_t no, sub_no;
    a_uint32_t cmd_deepth = 0;

    *cmd_index = GCMD_DESC_NO_MATCH;
    *cmd_index_sub = GCMD_DESC_NO_MATCH;

    if (cmd_str[0] == NULL)
        return cmd_deepth;

    for (no = 0; GCMD_DESC_VALID(no); no++) {
        if (strcasecmp(cmd_str[0], GCMD_NAME(no)))
            continue;

        for (sub_no = 0; GCMD_SUB_DESC_VALID(no, sub_no); sub_no++) {
            if (cmd_str[1] != NULL && cmd_str[2] != NULL) {

                if (GCMD_SUB_NAME(no, sub_no) && GCMD_SUB_ACT(no, sub_no)
                    && !strcasecmp(cmd_str[1], GCMD_SUB_NAME(no, sub_no))
                    && !strcasecmp(cmd_str[2], GCMD_SUB_ACT(no, sub_no))) {
                    *cmd_index = no;
                    *cmd_index_sub = sub_no;
                    cmd_deepth = 3;
                    return cmd_deepth;
                } 

                else if (!GCMD_SUB_NAME(no, sub_no) && GCMD_SUB_ACT(no, sub_no)
                      && !strcasecmp(cmd_str[1], GCMD_SUB_ACT(no, sub_no))) {
                    *cmd_index = no;
                    *cmd_index_sub = sub_no;
                    cmd_deepth = 2;
                    return cmd_deepth;
                }
            } else if (cmd_str[1] != NULL && cmd_str[2] == NULL) {

                if (!GCMD_SUB_NAME(no, sub_no) && GCMD_SUB_ACT(no, sub_no)
                    && !strcasecmp(cmd_str[1], GCMD_SUB_ACT(no, sub_no))) {
                    *cmd_index = no;
                    *cmd_index_sub = sub_no;
                    cmd_deepth = 2;
                    return cmd_deepth;
                }
            }
        }
    }

    return cmd_deepth;
}

static a_uint32_t *cmd_parse(char *cmd_str, int *cmd_index, int *cmd_index_sub)
{
    int cmd_nr = 0;
    a_uint32_t *arg_val = ioctl_argp;
    char *tmp_str[CMDSTR_ARGS_MAX];

    if (cmd_str == NULL)
        return NULL;

    memset(arg_val, 0, CMDSTR_ARGS_MAX * sizeof (a_uint32_t));

    /* split string into array */
    if ((tmp_str[cmd_nr] = (void *) strtok(cmd_str, " ")) == NULL)
        return NULL;

    /*handle help */
    if (!strcasecmp(tmp_str[cmd_nr], "help")) {
        dprintf("input ? get help\n\n");
        return NULL;
    }

    while (tmp_str[cmd_nr]) {
        if (++cmd_nr == 3)
            break;
        tmp_str[cmd_nr] = (void *) strtok(NULL, " ");
    }

    /*commond string lookup */
    int cmd_depth = cmd_lookup(tmp_str, cmd_index, cmd_index_sub);

    if (*cmd_index == GCMD_DESC_NO_MATCH || *cmd_index_sub == GCMD_DESC_NO_MATCH) {
        dprintf("invalid or incomplete command.\n\n");
        return NULL;
    }

    /*parse param */
    cmd_nr = 0;
    if (cmd_depth == 2) {
        tmp_str[cmd_nr] = tmp_str[2];
        cmd_nr++;
    }

    tmp_str[cmd_nr] = (void *) strtok(NULL, " ");
    while (tmp_str[cmd_nr]) {
        if (++cmd_nr == CMDSTR_ARGS_MAX)
            break;
        tmp_str[cmd_nr] = (void *) strtok(NULL, " ");
    }

    arg_val[0] = GCMD_SUB_API(*cmd_index, *cmd_index_sub);
    arg_val[1] = (a_uint32_t) ioctl_buf;

    int rtn_code;
    if (arg_val[0] < SW_API_MAX) {
        /*api command parse */
        rtn_code = cmd_parse_api(tmp_str, arg_val);

    } else if (arg_val[0] > SW_API_MAX) {
        /*user command parse */
        rtn_code = cmd_parse_sw(tmp_str, arg_val);

    } else {
        rtn_code = SW_BAD_PARAM;
    }

    if(rtn_code != SW_OK) {
        cmd_print_error(rtn_code);
        
        if(rtn_code == SW_BAD_PARAM)
           cmd_print_usage(*cmd_index, *cmd_index_sub);

        return NULL;    
    }

    return arg_val;
}

static int cmd_exec(a_uint32_t *arg_val, int cmd_index, int cmd_index_sub)
{
    a_uint32_t api_id = arg_val[0];
    sw_error_t rtn = SW_OK;

    if( api_id < SW_API_MAX ) {
        rtn = cmd_exec_api(arg_val);

    } else if ((api_id > SW_API_MAX ) && (api_id < SW_CMD_MAX)) {
        if (GCMD_SUB_FUNC(cmd_index, cmd_index_sub))
            rtn = GCMD_SUB_FUNC(cmd_index, cmd_index_sub)(arg_val);
    } else {
        rtn = SW_BAD_PARAM;
    }
    
    if(rtn != SW_OK)
        cmd_print_error(rtn);
    //else
		//dprintf("\n\n");
	
    return 0;
}

static int cmd_socket_init()
{
    sw_error_t rv;

    init_cfg.reg_mode = HSL_MDIO;
    init_cfg.reg_func.mdio_set = NULL;
    init_cfg.reg_func.mdio_get = NULL;

    rv = ssdk_init(0, &init_cfg);
    return (int)rv;
}

static sw_error_t cmd_init(void)
{
    ioctl_buf = (a_uint32_t *) malloc(IOCTL_BUF_SIZE);
    ioctl_argp = (a_uint32_t *) malloc(CMDSTR_ARGS_MAX * sizeof (a_uint32_t));
       
    return SW_OK;
}

static sw_error_t cmd_exit(void)
{
    free(ioctl_buf);
    free(ioctl_argp);
    return SW_OK;
}

static sw_error_t cmd_run_one(char *cmd_str)
{
    a_uint32_t *arg_list;
    int cmd_index = 0, cmd_index_sub = 0;

    if ((arg_list = cmd_parse(cmd_str, &cmd_index, &cmd_index_sub)) != NULL) {
        cmd_exec(arg_list, cmd_index, cmd_index_sub);
    }
    
    return SW_OK;
}

int cmd_is_batch(const char *cmd_str)
{
    char batch_cmd[] = "run";
    
    if(!strncmp(cmd_str, batch_cmd, strlen(batch_cmd)))
        return 1;
    return 0;    
}

static void cmd_batch_help(void)
{
    dprintf("usage:run <cmd_file> <result_file>\n");
}

static sw_error_t cmd_run_batch (char *cmd_str)
{
    FILE *in_fd = NULL;
    char * line = NULL;
    char *tmp_str[2];

    if (cmd_str == NULL)
        return SW_BAD_PARAM;
    
    /*usage: run cmd result*/
    if((tmp_str[0] = (void *) strtok(cmd_str, " ")) == NULL)
         return SW_BAD_PARAM;   
    
    /*check again*/
    if(!cmd_is_batch(tmp_str[0]))
        return SW_BAD_PARAM;

    if((tmp_str[1] = (void *) strtok(NULL, " "))== NULL)
        return SW_BAD_PARAM;
    if((tmp_str[2] = (void *) strtok(NULL, " "))== NULL)
        return SW_BAD_PARAM;

    if((in_fd = fopen(tmp_str[1], "r")) == NULL) {
        dprintf("can't open cmd file %s\n", tmp_str[1]);
        return SW_FAIL;
    }
    if((out_fd = fopen(tmp_str[2], "w+")) == NULL) {
        dprintf("can't open result file %s\n", tmp_str[2]);
        return SW_FAIL;
    }   

    size_t len = 0;
    ssize_t read;

    set_talk_mode(0);
    while ((read = getline(&line, &len, in_fd)) != -1) {
         //dprintf("(%d)%s",read, line);
        if(read <= 1 ) continue;

        if(line[strlen(line)-1] == '\n');
            line[strlen(line)-1] = '\0';
        
        if(!strncmp(line, "echo", 4)) {
            dprintf("%s\n", line+strlen("echo "));
            continue;
        }
        cmd_run_one(line);
    }
    set_talk_mode(1);
    
    if (line) free(line);
    
    fclose(out_fd);
    fclose(in_fd); 
    out_fd = 0;
    in_fd =0;
    
    return SW_OK;

}

static sw_error_t cmd_args(char *cmd_str, int argc, const char *argv[])
{
    /*quiet mode*/
    set_talk_mode(0);
    
    if(cmd_is_batch(argv[0]))
    {   
        if(argc != 3) {
            cmd_batch_help();
            return SW_FAIL;
        }
        
        sprintf(cmd_str, "%s %s %s", argv[0], argv[1], argv[2]);
        cmd_run_batch(cmd_str);

    } else {
        int argi;
        for(argi = 0; argi < argc; argi++) 
        {
            strcat(cmd_str, argv[argi]);
            strcat(cmd_str, " ");
        }
        cmd_run_one(cmd_str);
    }

    return SW_OK;
}

int cmd_is_exit(char *cmd_str)
{
    if ((!strcasecmp(cmd_str, "q")) || (!strcasecmp(cmd_str, "quit"))) {
        return 1;
    }
    return 0;
}

void cmd_welcome()
{
    char *ver = "", *date = "";
#ifdef VERSION
	ver = VERSION;
#endif

#ifdef BUILD_DATE
	date = BUILD_DATE;
#endif

    dprintf("Jssdk version: v%s, build date: %s\n", ver, date);
}

void cmd_help(void)
{
	printf("Usage: jssdk_sh [option] [commands]\n");
	printf("\n");
	printf("-h     Help.\n");
	printf("-i     Enable chip hardware init when jssdk init.\n");
	printf("\n");	
}

int main(int argc, char *argv[])
{
    char cmd_str[CMDSTR_BUF_SIZE];
    sw_error_t rv;
	a_bool_t chip_init_enable = A_FALSE;
	a_bool_t run_background   = A_FALSE;
	int f;
	static const struct option options[] = {
		{.name = "help",.val = 'h'},
		{.name = "init",.val = 'i'},
		{.name = "background",.val = 'b'},
		{0}
	};

	while ((f = getopt_long(argc, argv, "ihb", options, NULL)) != EOF) {
		switch (f) {
		case 'h':
			cmd_help();
			return 0;
		case 'i':
			chip_init_enable = A_TRUE;
			break;
		case 'b':
			run_background = A_TRUE;
			break;
		default:
			fprintf(stderr, "Unknown option '%c'\n", f);
			cmd_help();
			goto exit;
		}
	}

	cmd_init();

    init_cfg.reg_mode = HSL_MDIO;
    init_cfg.reg_func.mdio_set = NULL;
    init_cfg.reg_func.mdio_get = NULL;
	init_cfg.en_init_chip = chip_init_enable;

    if((rv = ssdk_init(0, &init_cfg)) != SW_OK) {
		fprintf(stderr, "ssdk_init failed, rc=%d\n", rv);
		goto exit;
	}


    if(run_background == A_TRUE)
    {
    	while(1)
    	{
    		sleep(5);
    	}
    	goto exit;
    }

	if((chip_init_enable == A_TRUE) && (argc == optind)) {
		dprintf("Initialized %s chip configuration.\n", (chip_init_enable == A_TRUE)? "with" : "without");
		goto exit;
	}

	argc -= optind;
	argv += optind;
	
    if(argc > 0) { 

        memset(cmd_str, 0, sizeof(cmd_str));
        cmd_args(cmd_str, argc, argv);
        cmd_exit();
        return 0;
    }

	cmd_welcome();
	dprintf("Initialized %s chip configuration.\n", (chip_init_enable == A_TRUE)? "with" : "without");
	
    while (1) {
        memset(cmd_str, 0, sizeof(cmd_str));

        if(next_cmd(cmd_str) == 0)
            continue;

        if (cmd_is_exit(cmd_str))
            break;

        if(cmd_is_batch(cmd_str)) {
            if(cmd_run_batch(cmd_str)!= SW_OK)
                cmd_batch_help();
        } else {
            cmd_run_one(cmd_str);
        }
    }

exit:    
    cmd_exit();
	
    return 0;
}

